package ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.service.imp;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.persistence.model.AddressType;
import static ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.persistence.model.AddressType.*;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.persistence.model.Device;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.persistence.model.DeviceType;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.persistence.model.Field;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.persistence.model.SimpleUser;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.persistence.model.SoundRequest;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.persistence.model.User;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.service.DeviceChangeListenerFactory;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.service.DeviceService;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.service.SystemService;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.service.UserDataService;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.service.factories.DeviceServiceFactory;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.service.factories.UserDataServiceFactory;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.persistence.DeviceDAO;
import ar.edu.utn.frsf.proyectofinal.sistemadecontrolmodbus.persistence.UserDAO;

@Component("systemService")
public class SystemServiceImp implements SystemService {

	//TODO: Falta metodo buscar Device
	@Autowired(required = true)
	private UserDAO userDAO;
	@Autowired(required = true)
	private DeviceDAO deviceDAO;
	@Autowired(required = true)
	private UserDataServiceFactory userDataServiceFactory;
	@Autowired(required = true)
	private DeviceServiceFactory deviceServiceFactory;
	@Autowired(required = true)
	private DeviceChangeListenerFactory deviceChangeListenerFactory;
	
	private Map<String,UserDataService> loggedusers;
	private Map<Integer,DeviceService>  devservicecreated;
	private boolean active = false;
	
	public SystemServiceImp(){
		activate();
	}
	
	@Override
	public synchronized UserDataService logIn(String username, String pass) {
		
		User user = userDAO.findByUsername(username);
		if(user == null || !user.getPassword().equals(pass))
			return null;
		//TODO: Chequear condicion de competencia
		if(loggedusers.containsKey(username))
			return loggedusers.get(username);
		UserDataService newuser = userDataServiceFactory.createService(user,this);
		loggedusers.put(username, newuser);
		return newuser;
	}

	@Override
	public synchronized List<DeviceService> getDeviceServices(List<Device> userdevices) {
		List<DeviceService> ret = new ArrayList<DeviceService>();
		for (Device udev : userdevices) {
			DeviceService devserv;
			if(!devservicecreated.containsKey(udev.getID())){
				devserv = deviceServiceFactory.create(udev);
				devserv.addDeviceChangeListener(deviceChangeListenerFactory.create(udev));
				devservicecreated.put(udev.getID(), devserv);
			}
			else {
				devserv = devservicecreated.get(udev.getID());
			}
			ret.add(devserv);
		}
		return ret;
	}

	/**
	 * @return the userdao
	 */
	public UserDAO getUserDAO() {
		return userDAO;
	}

	/**
	 * @param userdao the userdao to set
	 */
	public void setUserDAO(UserDAO userdao) {
		this.userDAO = userdao;
	}

	public DeviceDAO getDeviceDAO() {
		return deviceDAO;
	}

	public void setDeviceDAO(DeviceDAO deviceDAO) {
		this.deviceDAO = deviceDAO;
	}

	/**
	 * @return the userDataServiceFactory
	 */
	public UserDataServiceFactory getUserDataServiceFactory() {
		return userDataServiceFactory;
	}

	/**
	 * @param userDataServiceFactory the userDataServiceFactory to set
	 */
	public void setUserDataServiceFactory(
			UserDataServiceFactory userDataServiceFactory) {
		this.userDataServiceFactory = userDataServiceFactory;
	}

	/**
	 * @return the deviceServiceFactory
	 */
	public DeviceServiceFactory getDeviceServiceFactory() {
		return deviceServiceFactory;
	}

	/**
	 * @param deviceServiceFactory the deviceServiceFactory to set
	 */
	public void setDeviceServiceFactory(DeviceServiceFactory deviceServiceFactory) {
		this.deviceServiceFactory = deviceServiceFactory;
	}

	/**
	 * @return the devchangefact
	 */
	public DeviceChangeListenerFactory getDeviceChangeListenerFactory() {
		return deviceChangeListenerFactory;
	}

	/**
	 * @param devchangefact the devchangefact to set
	 */
	public void setDeviceChangeListenerFactory(DeviceChangeListenerFactory devchangefact) {
		this.deviceChangeListenerFactory = devchangefact;
	}

	@Override
	public synchronized void activate() {
		if(!active){
			loggedusers = new HashMap<String, UserDataService>();
			loggedusers = Collections.synchronizedMap(loggedusers);
			devservicecreated = new HashMap<Integer, DeviceService>();
			devservicecreated = Collections.synchronizedMap(devservicecreated);
			active = true; 
		}
	}

	@Override
	public synchronized void deactivate() {
		if(active){
			active = false;
			for (DeviceService dev : devservicecreated.values()) {
				dev.shutDown();
			}
			devservicecreated = null;
		}		
	}

	@Override
	public boolean isActive() {
		return active;
	}

	@Override
	public List<DeviceType> getSysDeviceTypes() {
		return deviceDAO.listDeviceTypes();
	}

	@Override
	public void saveType(DeviceType type) {
		
		List<SoundRequest> sounds =  createSounding(type.getFields());
		for (SoundRequest soundRequest : sounds) {
			soundRequest.setDeviceType(type);
		}
		type.getSoundRequests().clear();
		type.getSoundRequests().addAll(sounds);
		deviceDAO.saveOrUpdateType(type);		
	}

	private List<SoundRequest> createSounding(List<Field> fields) {
		List<SoundRequest> ret = new ArrayList<SoundRequest>();
		List<Field> typefields;
		
		typefields = getFields(fields,COIL);
		typefields = orderByAddress(typefields);
		ret.addAll(getSoundRequests(typefields,COIL));
		
		typefields = getFields(fields,DISCRETE);
		typefields = orderByAddress(typefields);
		ret.addAll(getSoundRequests(typefields,DISCRETE));
		
		typefields = getFields(fields,HOLDING_REGISTER);
		typefields = orderByAddress(typefields);
		ret.addAll(getSoundRequests(typefields,HOLDING_REGISTER));
		
		typefields = getFields(fields,INPUT_REGISTER);
		typefields = orderByAddress(typefields);
		ret.addAll(getSoundRequests(typefields,INPUT_REGISTER));
		
		return ret;
	}

	private Collection<SoundRequest> getSoundRequests(List<Field> fields, AddressType type) {
		List<SoundRequest> ret = new ArrayList<SoundRequest>();
		int maxBulk;
		if(type == COIL || type == DISCRETE)
			maxBulk = 2000;
		else
			maxBulk = 125;
		Object[] iterator = fields.toArray();
		
		for (int i = 0; i < iterator.length; i++) {
			
			Integer address, currentAddress, size;
			address = ((Field)iterator[i]).getAddress();
			currentAddress = address;
			size = 0;
			int j;
			System.out.println("BASE ADDRESS: " + address);
			for (j = i; j < iterator.length && (j - i) < maxBulk; j++) {
				
				System.out.println("ADDRESS: " + ((Field)iterator[j]).getAddress());
				if(((Field)iterator[j]).getAddress().equals(currentAddress)){
					System.out.println("Condition");
					size++;
					currentAddress++;
				}
				else if(((Field)iterator[j]).getAddress().equals(currentAddress - 1))
					continue;
				else
					break;
			}
			
			i = j - 1;
			
			SoundRequest newSound = new SoundRequest();
			newSound.setAddress(address);
			newSound.setAddressType(type);
			newSound.setCount(new Integer(size));
			ret.add(newSound);
			
		}
		
		return ret;
	}

	private List<Field> orderByAddress(List<Field> coils) {
		 Collections.sort(coils, new Comparator<Field>() {

			@Override
			public int compare(Field o1, Field o2) {
				    return o1.getAddress().compareTo(o2.getAddress());
			}
		});
		 return coils;
	}

	private List<Field> getFields(List<Field> fields,AddressType type) {
		List<Field> ret = new ArrayList<Field>();
		for (Field field : fields) {
			if(field.getAddressType() == type)
				ret.add(field);
		}
		return ret;
	}
	
	@Override
	public void deleteDeviceType(DeviceType type) {
		deviceDAO.deleteType(type);
		
	}

	@Override
	public List<Device> getSysDevices() {
		return deviceDAO.listDevices();
	}

	@Override
	public void saveDevice(Device device) {
		deviceDAO.saveOrUpdateDevice(device);	
		
	}

	@Override
	public void deleteDevice(Device device) {
		deviceDAO.deleteDevice(device);
	}

	@Override
	public List<SimpleUser> getSysUsers() {
		return userDAO.listUsers();
	}

	@Override
	public void saveUser(SimpleUser user) {

		userDAO.saveOrUpdateUser(user);	
		
	}

	@Override
	public void deleteUser(SimpleUser user) {
		userDAO.deleteUser(user);
		
	}

}
